# -*-coding:utf-8-*-

import pyrestful.rest
from pyrestful import mediatypes
from pyrestful.rest import get, post, put, delete
from pymongo.collection import ReturnDocument
from utils.auth import authenticated
import time, json
from datetime import datetime, timedelta
import handlers.base

'''
device_statistics document structure
{
    "device_id": "DEVICE_ID",
    "date": "%Y-%m-%d",
    "type": "water",
    "total": {
        "duration": INT,
        "input": INT,
        "output": INT,
    },
    "detail": [
        {
            "time": "%H:%M:%S",
            "duration": INT,
            "input": INT,
            "output": INT,
        },
        ...
    ]
}
'''


class DevicesMessagesHandler(handlers.base.BaseHandler):
    @authenticated
    @get(_path='/api/devices', _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device_list(self):
        db = self.settings['db']
        devices = db.get_collection('devices')
        try:
            cursor = devices.find({'owner': self.user_id}, {'_id': 0, 'owner': 0})
            result = []
            for device in cursor:
                result.append(device)
            self.finish(json.dumps(result))
        except Exception as e:
            print e
            self.set_status(400, str(e))

    @authenticated
    @post(_path='/api/devices', _produces=mediatypes.APPLICATION_JSON)
    def create_device(self):
        db = self.settings['db']
        devices = db.get_collection('devices')
        try:
            request = json.loads(self.request.body)
            if not self.is_keys_required_present(
                    ['device_id', 'vendor_id', 'model', 'type', 'name', 'location', 'location_name'], request.keys(),
                    equal=True):
                return
            device = request
            device['owner'] = self.user_id
            if 0 == devices.find({'device_id': device['device_id']}).count():
                devices.insert_one(device)
                self.set_status(201)
            else:
                self.set_status(409, 'device already exists')
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @get(_path='/api/devices/{device_id}', _types=[str], _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device(self, device_id):
        db = self.settings['db']
        devices = db.get_collection('devices')
        try:
            result = devices.find_one({'device_id': device_id}, {'_id': 0, 'owner': 0})
            if result is not None:
                self.set_status(200)
                self.finish(result)
            else:
                self.set_status(404)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @put(_path='/api/devices/{device_id}', _types=[str], _produces=mediatypes.APPLICATION_JSON)
    def update_device(self, device_id):
        db = self.settings['db']
        devices = db.get_collection('devices')
        try:
            modification = json.loads(self.request.body)
            key_set_supported = set(['name', 'location', 'location_name'])
            key_set = set(modification.keys())
            key_set_unsupported = key_set - key_set_supported
            if key_set_unsupported:
                self.set_status(400, 'invalid key[s]: %s' % str(list(key_set_unsupported)))
                return
            res = devices.find_one_and_update({'device_id': device_id, 'owner': self.user_id},
                                              {'$set': modification})
            if res is not None:
                self.set_status(201)
            else:
                self.set_status(404)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @delete(_path='/api/devices/{device_id}', _types=[str], _produces=mediatypes.APPLICATION_JSON)
    def delete_device(self, device_id):
        db = self.settings['db']
        devices = db.get_collection('devices')
        devices_states = db.get_collection('devices_states')
        res = devices.find_one_and_delete({'device_id': device_id, 'owner': self.user_id})
        if res is None:
            self.set_status(404)
        else:
            devices_states.find_one_and_delete({'device_id': device_id})
            self.set_status(204)

    def is_owner_of_device(self, device_id):
        db = self.settings['db']
        devices = db.get_collection('devices')
        res = devices.find_one({'device_id': device_id, 'owner': self.user_id})
        if res is None:
            self.set_status(404, 'invalid device id')
            return False
        else:
            return True

    '''
    States
    '''

    @authenticated
    @get(_path='/api/devices/{device_id}/states', _types=[str], _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device_states(self, device_id):
        if not self.is_owner_of_device(device_id):
            return
        db = self.settings['db']
        devices_states = db.get_collection('devices_states')
        try:
            result = devices_states.find_one({'device_id': device_id}, {'_id': 0, 'device_id': 0})
            if result is None:
                result = {}
            self.set_status(200)
            self.finish(result)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @put(_path='/api/devices/{device_id}/states', _types=[str],
         _produces=mediatypes.APPLICATION_JSON)
    def update_device_states(self, device_id):
        if not self.is_owner_of_device(device_id):
            return
        self.tmp_update_device_states(device_id)

    @put(_path='/tmp/api/devices/{device_id}/states', _types=[str],
         _produces=mediatypes.APPLICATION_JSON)
    def tmp_update_device_states(self, device_id):
        db = self.settings['db']
        devices_states = db.get_collection('devices_states')
        try:
            modification = json.loads(self.request.body)
            modification['heartbeat'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            devices_states.find_one_and_update({'device_id': device_id}, {'$set': modification}, upsert=True)
            self.set_status(201)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @get(_path='/api/devices/{device_id}/states/{key}', _types=[str, str], _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device_states_by_key(self, device_id, key):
        if not self.is_owner_of_device(device_id):
            return
        db = self.settings['db']
        devices_states = db.get_collection('devices_states')
        try:
            result = devices_states.find_one({'device_id': device_id}, {key: 1})
            if result is None:
                result = {}
            else:
                del result['_id']
            self.set_status(200)
            self.finish(result)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @get(_path='/api/devices/{device_id}/states/{array_key}/{array_index}', _types=[str, str, int],
         _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device_states_by_array_key_index(self, device_id, array_key, array_index):
        if not self.is_owner_of_device(device_id):
            return
        db = self.settings['db']
        devices_states = db.get_collection('devices_states')
        try:
            # NOTE: set '_id': 1 to filter others not present out, reduce consumption of bandwidth
            result = devices_states.find_one({'device_id': device_id},
                                             {'_id': 1, array_key: {'$slice': [array_index, 1]}})
            if result is None:
                result = {}
            else:
                result = result[array_key][0]
            self.set_status(200)
            self.finish(result)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @put(_path='/api/devices/{device_id}/states/{array_key}/{array_index}', _types=[str, str, int],
         _produces=mediatypes.APPLICATION_JSON)
    def update_device_states_by_array_key_index(self, device_id, array_key, array_index):
        if not self.is_owner_of_device(device_id):
            return
        db = self.settings['db']
        devices_states = db.get_collection('devices_states')
        try:
            modification = json.loads(self.request.body)
            heartbeat = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            devices_states.find_one_and_update({'device_id': device_id},
                                               {'$set': {'%s.%d' % (array_key, array_index): modification, 'heartbeat': heartbeat}},
                                               upsert=True,
                                               return_document=ReturnDocument.AFTER)
            self.set_status(201)
        except Exception as e:
            self.set_status(400, str(e))

    '''
    Services
    '''

    @authenticated
    @put(_path='/internal/api/devices/{device_id}/services/{service_name}', _types=[str, str],
         _produces=mediatypes.APPLICATION_JSON)
    def internal_update_device_service(self, device_id, service_name):
        if not self.is_owner_of_device(device_id):
            return
        db = self.settings['db']
        devices_services = db.get_collection('devices_services')
        try:
            request = json.loads(self.request.body)
            recharge_days = int(request['recharge_days'])
            recharge_seconds = int(timedelta(days=recharge_days).total_seconds())
            cur_time = int(time.time())

            # TODO: 考虑多进程并发情况/ZK LOCK
            result = devices_services.find_one({'device_id': device_id})
            if result is None:
                service = {}
                service['name'] = service_name
                service['expire_time'] = cur_time + recharge_seconds
                service['total_used_time'] = 0
                service['first_pay_time'] = cur_time
                service['last_pay_time'] = cur_time

                device = {}
                services = [service]
                device['device_id'] = device_id
                device['services'] = services

                devices_services.insert_one(device)
                self.set_status(201)
            else:
                print result
                service_found = False
                i = 0
                for service in result['services']:
                    if service['name'] == service_name:
                        if service['expire_time'] < cur_time:
                            service['total_used_time'] += service['expire_time'] - service['last_pay_time']
                        else:
                            service['total_used_time'] += cur_time - service['last_pay_time']
                        service['last_pay_time'] = cur_time
                        service['expire_time'] += recharge_seconds
                        service_found = True
                        devices_services.find_one_and_update({'device_id': device_id},
                                                             {'$set': {'services.%d' % i: service}})
                        break
                    i = i + 1
                if not service_found:
                    service = {}
                    service['name'] = service_name
                    service['expire_time'] = cur_time + recharge_seconds
                    service['total_used_time'] = 0
                    service['first_pay_time'] = cur_time
                    service['last_pay_time'] = cur_time
                    devices_services.find_one_and_update({'device_id': device_id}, {'$push': {'services': service}})
                self.set_status(201)
        except Exception as e:
            self.set_status(400, str(e))

    @authenticated
    @get(_path='/api/devices/{device_id}/services', _types=[str, str], _produces=mediatypes.APPLICATION_JSON)
    def retrieve_device_service_list(self, device_id):
        if not self.is_owner_of_device(device_id):
            return
        self.tmp_retrieve_device_service_list(device_id)

    @get(_path='/tmp/api/devices/{device_id}/services', _types=[str, str], _produces=mediatypes.APPLICATION_JSON)
    def tmp_retrieve_device_service_list(self, device_id):
        db = self.settings['db']
        devices_services = db.get_collection('devices_services')
        services = []
        result = devices_services.find_one({'device_id': device_id})
        if result is not None:
            cur_time = int(time.time())
            services = result['services']
            for service in services:
                if service['expire_time'] < cur_time:
                    service['total_used_time'] += service['expire_time'] - service['last_pay_time']
                else:
                    service['total_used_time'] += cur_time - service['last_pay_time']
        self.finish(json.dumps(services))
